/*
*    Clean AJAX Engine v4.0
*    Copyright (C) 2005-2006 Carlos Eduardo Goncalves (cadu.goncalves@gmail.com)
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program; if not, write to the Free Software
*    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

/**
* Provides a class used to read/write XMLRPC web services data streams.
* This implementation is focused on web service client requirements,
* it means that it is only able to write requests and read responses.
* This implementation support all XMLRPC datatypes.
* @author Carlos Eduardo Goncalves
*/

/**
*<p>Xmlrpc stream constructor.</p>
*/
function XmlRpcStream(){
  /** <p>Register if there is a fault.</p> */	
  XmlRpcStream.prototype.faultValue = undefined;
  /** <p>Register if current node is a name.</p> */	  
  XmlRpcStream.prototype.getName = false;
  /** <p>Hold a parsed property.</p> */	  
  XmlRpcStream.prototype.propertyName = '';
  /** <p>Store parsed params.</p> */	  
  XmlRpcStream.prototype.params = [];	
}

/** <p>Xmlrpc document prolog.</p> */
XmlRpcStream.PROLOG = "<?xml version=\"1.0\"?>\n";

/** <p>Xmlrpc methodCall node template.</p> */
XmlRpcStream.REQUEST = "<methodCall>\n<methodName>${METHOD}</methodName>\n<params>\n${DATA}</params>\n</methodCall>";

/** <p>Xmlrpc param node template.</p> */
XmlRpcStream.PARAM = "<param>\n<value>\n${DATA}</value>\n</param>\n";

/** <p>Xmlrpc array node template.</p> */
XmlRpcStream.ARRAY = "<array>\n<data>\n${DATA}</data>\n</array>\n";

/** <p>Xmlrpc struct node template.</p> */
XmlRpcStream.STRUCT = "<struct>\n${DATA}</struct>\n";

/** <p>Xmlrpc member node template.</p> */
XmlRpcStream.MEMBER = "<member>\n${DATA}</member>\n";

/** <p>Xmlrpc name node template.</p> */
XmlRpcStream.NAME = "<name>${DATA}</name>\n";

/** <p>Xmlrpc value node template.</p> */
XmlRpcStream.VALUE = "<value>\n${DATA}</value>\n";

/** <p>Xmlrpc scalar node template (int, i4, double, string, boolean, base64, dateTime.iso8601).</p> */
XmlRpcStream.SCALAR = "<${TYPE}>${DATA}</${TYPE}>\n"; 

/**
* <p>Write a XMLRPC document.</p>
* @param method
*		<code>RemoteMethod</code> value object.
* @return
*		<code>XMLDocument</code> instance that is a valid XMLRPC document.
*/
XmlRpcStream.prototype.write = function(method){
  try{		
    if(!Engine.assertType(method, RemoteMethod))
	  return null;
    var xml_params = "";
    for(var i = 0; i < method.params.length; i++)
      xml_params += XmlRpcStream.PARAM.replace('${DATA}', this.serialize(method.params[i]));	
    var xml_call = XmlRpcStream.REQUEST.replace("${METHOD}", method.getName());	
    xml_call = XmlRpcStream.PROLOG + xml_call.replace('${DATA}', xml_params); 
	Console.trace(xml_call, Console.RPC_DATA);
    var dom = (typeof DOMParser != "undefined") ? new DOMParser() : Engine.buildActiveX(Engine.ACTIVEX_DOM);		
    if(typeof DOMParser != "undefined")
      return dom.parseFromString(xml_call, "text/xml");    
    else{
	  dom.loadXML(xml_call);
      return dom;
	}
  } catch(e){Engine.reportException(null, e);}     
}

/**
* <p>Read a XMLRPC document.</p>
* @param doc
*		<code>XMLDocument</code> to read.
* @return
*		JavaScript object tree.
*/
XmlRpcStream.prototype.read = function(doc){  
  this.faultValue = undefined;
  this.getName = false;
  this.propertyName = '';
  this.params = [];
  if(doc)
    for(var i = 0; i < doc.childNodes.length; i++)
      this.unserialize(doc.childNodes[i], 0);
  return this.params[0];
}

/**
* <p>Encode a JavaScript object to a compatible XMLRPC representation.</p>
* @param data
*		JavaScript object to be encoded.
* @return
*		<code>String</code> with XMLRPC object representation.
*/
XmlRpcStream.prototype.serialize = function(data){
  try{
    var type = this.getDataTag(data);
    var scalar_type = XmlRpcStream.SCALAR.replace(/\$\{TYPE\}/g, type);
	var xml = "";
    switch(type){
      case 'struct':
        var member = "";	  
        for(var i in data){
           var value = "";
           value += XmlRpcStream.NAME.replace('${DATA}', i);
           value += XmlRpcStream.VALUE.replace('${DATA}', this.serialize(data[i]));
           member += XmlRpcStream.MEMBER.replace('${DATA}', value);		 
	    }
	    xml = XmlRpcStream.STRUCT.replace('${DATA}', member); break;	  
	  case 'array':
	    var value = "";
	    for(var i = 0; i < data.length; i++){
          value += XmlRpcStream.VALUE.replace('${DATA}', this.serialize(data[i])); 
	    }
        xml = XmlRpcStream.ARRAY.replace('${DATA}', value); break;
	  case 'dateTime.iso8601':     
	    xml = scalar_type.replace('${DATA}', data.toIso8601()); break;	
	  case 'boolean': 
	    xml = scalar_type.replace('${DATA}', (data == true) ? 1 : 0); break;
	  case 'base64':
	    xml = scalar_type.replace('${DATA}', data.encode()); break;	
      default : 
	    xml = scalar_type.replace('${DATA}', data); break;
    }
    return xml;
  } catch(e){Engine.reportException(null, e);}    
}

/**
* <p>Decode XMLRPC representation to a compatible JavaScript object.</p>
* @param node
*		<code>XMLDocument</code> current <code>Node</code>.
* @param parent
*		<code>XMLDocument</code> current parent <code>Node</code>.
* @return
*		JavaScript object tree. 
*/
XmlRpcStream.prototype.unserialize = function(node, parent){ 
  if(node.nodeType == 1){
	var obj = null;
	var tag = node.tagName.toLowerCase();
    switch(tag){  
      case 'fault':
	    this.faultValue = true; 
		break;	  		
      case 'name':
	    this.getName = true;
		break;
	  default: 
	    obj = this.getTagData(tag);
		break;
    }
	if(obj != null){
      this.params.push(obj);	  
      if(tag == 'struct' || tag == 'array'){
		if(this.params.length > 1){  
          switch(this.getDataTag(this.params[parent])){		  
            case 'struct': 	
              this.params[parent][this.propertyName] = this.params[this.params.length - 1]; 
			  break;
            case 'array': 	 
              this.params[parent].push(this.params[this.params.length - 1]); 
			  break;	 
          }		
		}
        var parent = this.params.length - 1;		  	   
	  }
	}
    for(var i = 0; i < node.childNodes.length; i++){	
       this.unserialize(node.childNodes[i], parent);
    } 
  }
  if( (node.nodeType == 3) && (/[^\t\n\r ]/.test(node.nodeValue)) ){
    if(this.getName == true){
	  this.propertyName = node.nodeValue;
      this.getName = false;
	}
	else{
      switch(this.getDataTag(this.params[this.params.length - 1])){	   
	    case 'dateTime.iso8601':
	      this.params[this.params.length - 1] = Date.fromIso8601(node.nodeValue); 
		  break;
 	    case 'boolean':
		  this.params[this.params.length - 1] = (node.nodeValue == "1") ? true : false; 
		  break
 	    case 'int':
 	    case 'double':		
		  this.params[this.params.length - 1] = new Number(node.nodeValue); 
		  break
 	    case 'string':
		  this.params[this.params.length - 1] = new String(node.nodeValue); 
		  break
 	    case 'base64':
		  this.params[this.params.length - 1] = new Base64(node.nodeValue); 
		  break
      }
	  if(this.params.length > 1){  	  
        switch(this.getDataTag(this.params[parent])){		  
          case 'struct': 	
            this.params[parent][this.propertyName] = this.params[this.params.length - 1]; 
			break;
          case 'array': 	 
            this.params[parent].push(this.params[this.params.length - 1]); 
			break;	 
        }
	  }
	}
  }
}

/**
* <p>Get the tag name used to represent a JavaScript
* object in the XMLRPC protocol.</p>
* @param data
*		A JavaScript object.
* @return
*		<code>String</code> with XMLRPC object type.
*/
XmlRpcStream.prototype.getDataTag = function(data){
  try{
    var tag = typeof data;
    switch(tag.toLowerCase()){ 	  	  
      case 'number':
        tag = (Math.round(data) == data) ? "int" : "double";  
	    break;   
	  case 'object': 
        if(data.constructor == Base64)
	      tag = 'base64';
	    else	  	
        if(data.constructor == String)
	      tag = 'string';
	    else
	    if(data.constructor == Boolean)
	      tag = 'boolean';
	    else
	    if(data.constructor == Array)
	      tag = 'array';
	    else	  
	    if(data.constructor == Date)
	      tag = 'dateTime.iso8601';
	    else	  
        if(data.constructor == Number)
	      tag = (Math.round(data) == data) ? "int" : "double";  
	    else	  
	      tag = "struct"; 
	    break;
    }
    return tag;
  } catch(e){Engine.reportException(null, e);}    
} 

/**
* <p>Get JavaScript object type represented by 
* XMLRPC protocol tag.<p>
* @param tag
*		A XMLRPC tag name.
* @return
*		A JavaScript object.
*/
XmlRpcStream.prototype.getTagData = function(tag){
  var data = null;
  switch(tag){
    case 'struct':
      data = new Object(); 
	  break;
    case 'array':
      data = new Array(); 
	  break;
    case 'datetime.iso8601':
      data = new Date(); 
	  break;
    case 'boolean':
      data = new Boolean(); 
	  break;
    case 'int':
    case 'i4':
    case 'double':
      data = new Number(); 
	  break;	  
    case 'string':
      data = new String(); 
	  break;	  
    case 'base64':
      data = new Base64(); 
	  break;	    				
  }
  return data;
}